#coding=utf-8
import serial
import pynmea2
import time
import datetime
import math
import requests
import json
import logging

# zhh 自己编写的文件
import config


# 创建gps串口的句柄
SER = serial.Serial("/dev/ttyUSB0", 9600 )  

'''
function:
  从串口读取gps数据，和服务器查询到的站点的gps数据比对，如果距离达到一定程度，则判定达到(驶出)站点，此时，向服务器发送到达某站点的信息
 （这种方法彻底隔绝了服务器和树莓派之间直接交换gps数据的方式）

向服务发送的到站的报文格式：
    {'driver_id':123, 'bus_id':'a123', 'platform_name':'shshj', 'status': 1/2/3/4, 'type':'in/out'}

服务器返回报文：
    {'driver_id': 'xxxxxxxx', 'bus_id': 'xxxxxx','type': 'true/false'}
'''
def gps_send():

    while(config.GPS_STOP == 0):   # 循环的，每个一定时间读取gps数据，直到挑选到我们需要的那一行，然后进行站点距离判断

        '''
        # 旧v2.0版本实现
        # 当司机未上班打卡时，陷入死循环，直到司机上班打卡时跳出循环
        config.config_lock.acquire() # 加锁（防止出现，判断的时候是-1，执行清空函数时是非-1，导致有效数据被误清空）
        if (config.BUS_STATUS == -1):
            # 当司机下班打卡时，需要将数据库的车辆状态同时修改为 -1（只发送一次，然后就把OLD_BUS_STATUS改为-1）
            if config.OLD_BUS_STATUS != -1:
                send_come_message(0)
                config.OLD_BUS_STATUS = -1
                logging.debug('[gps_send] 司机下班打卡，同步车辆状态至数据库')
            print('[gps_send] 车辆暂时没有初始化，无法查询站点')
            clean_config()
            config.config_lock.release() # 解锁
            time.sleep(3)
            continue
        else:
            config.config_lock.release() # 解锁
        '''

        '''
        # 旧v1.0版本实现
        while (config.BUS_STATUS == -1):  # 当司机未上班打卡时，陷入死循环，直到司机上班打卡时跳出循环
            print('[gps_send] 车辆暂时没有初始化，无法查询站点')
            clean_config()
            time.sleep(3)
        '''
        if (config.BUS_STATUS == -1):
            # 当司机下班打卡时，需要将数据库的车辆状态同时修改为 -1(数据库为0)（只发送一次，然后就把OLD_BUS_STATUS改为-1）
            # if config.OLD_BUS_STATUS != -1:
            #     #send_come_message(0)   此方法移动到了qrcode文件中
            #     config.OLD_BUS_STATUS = -1
            #     logging.debug('[gps_send] 司机下班打卡，同步车辆状态至数据库')

            print('[gps_send] 车辆暂时没有初始化，无法查询站点')
            time.sleep(3)
            continue

        if config.BUS_STATUS == 0 and len(config.platform_name) == 0:  # 当车辆状态为0,且未查询站点时，都应让他陷入在这里，不得继续往下执行
            print('[gps_send] 车辆初始化成功，可以开始查询站点')
            logging.debug('[gps_send] 车辆初始化成功，可以开始查询站点')
            ret_num = platform_query()    # 首先向服务器查询所有站点信息
            if ret_num < 2:               # 如果站点数小于2，是不正常的，此时需要清除本地缓存，重新查询
                print('[gps_send]：站点信息错误，重新查询站点')
                logging.error('[gps_send]：站点信息错误，重新查询站点')
                config.platform_pk = []
                config.platform_name = []
                config.platform_longitude = []
                config.platform_latitude = []
                time.sleep(3)
                continue
        
        if config.BUS_STATUS == 0 and len(config.platform_name) > 0:  # 车辆需要从起始站发车，才能将状态从0改为 大于0, 此期间需要不断查询是否到达了起始站
            ret = start_bus()
            if ret == 0:
                time.sleep(3)
                continue
        '''
        while (config.BUS_STATUS == 0):  # 车辆需要从起始站发车，才能将状态从0改为 大于0, 此期间需要不断查询是否到达了起始站
            start_bus()
        '''
        config.config_lock.acquire() # 加锁（防止出现，判断时状态>0，但是由于二维码线程扫码，中途变成了<0，导致的同步错误）
        if config.BUS_STATUS > 0:        # 车辆处于运行状态，才会发送数据
            gps_value = read_gps()       # 读取一次有效的gps数据
            if gps_value == 1:           # 如果读取到的gps数据有效
                

                ret_plat_num = come_platform()          # 到站判断，返回到达的站点的下标
                if ret_plat_num >= 0:                    # 不等于 -1 说明到达了新站点
                    time0 = datetime.datetime.now()
                    change_bus_status(ret_plat_num, 1)  # 改变车辆状态
                    for i in range(3):
                        ret = send_come_message(ret_plat_num)     # 发送到站信息(尝试发3次，只要成功一次，就跳出循环)
                        if ret == 1:
                            break
                    config.HAVE_SEND_LEAVE_MESSAGE = 0  # 到达一个新站，发送过到站信息后，那么此站的离站信息就还没有发过了
                    config.OLD_PLATFORM = config.NOW_PLATFORM  # 保存当前站点
                    config.NOW_PLATFORM = ret_plat_num  # 更新当前站点
                    config.NEXT_PLATFORM = get_next_platform(config.NOW_PLATFORM)  # 更新下一站（只在到站时更新下一站）
                    print('[gps_send] 车辆状态  ', config.BUS_STATUS)
                    logging.debug('[gps_send] 车辆当前状态  '+str(config.BUS_STATUS))
                    time1 = datetime.datetime.now()
                    logging.debug('[gps_send] 到站检测耗时： ' + str(time1-time0) )

                leave_plat_num = leave_platform()              # 计算离开站点的下标
                if leave_plat_num >= 0:
                    time0 = datetime.datetime.now()
                    change_bus_status(leave_plat_num, 2)       # 改变车辆状态
                    for i in range(3):
                        ret = send_leave_message(leave_plat_num)         # 发送离站信息(尝试发3次，只要成功一次，就跳出循环)
                        if ret == 1:
                            break
                    config.HAVE_SEND_LEAVE_MESSAGE = 1  # 表明已经发送过离站信息
                    print('[gps_send] 车辆状态  ', config.BUS_STATUS)
                    logging.debug('[gps_send] 车辆当前状态  ' + str(config.BUS_STATUS) )
                    time1 = datetime.datetime.now()
                    logging.debug('[gps_send] 到站检测耗时： ' + str(time1-time0) )

                #time.sleep(2)
            else:                                    # 如果读取到的gps数据无效
                print('[gps_send] 本次读取的gps数据无效，已忽略')
                logging.debug('[gps_send] 本次读取的gps数据无效，已忽略')
        config.config_lock.release() # 解锁
        time.sleep(3)
    return



'''
function:
    清空车辆信息
'''
def clean_config():
    config.qrcode_count = 0
    config.old_BUS_ID = config.BUS_ID
    config.BUS_ID = config.blank                 
    config.DRIVER_ID = config.blank
    config.old_passagers = {'first', 'last'} 
    config.platform_pk = []
    config.platform_name = []
    config.platform_longitude = []
    config.platform_latitude = []


'''
function：
    车辆状态转变：根据车辆当前状态 1,2,3,4,和到站，离站的站点，来判断车辆下一阶段应当处于什么状态1,2,3,4
target_platform:
    即将 到达，或离开 的站点在列表的下标   
mode:
    1代表到站信号，2代表离站信号
修改：
    最终会修改值 config.BUS_STATUS
'''
def change_bus_status(target_platform, mode):
    if mode == 1:                 # 根据到站信号来判断下一状态
        if target_platform == 0:  # 到达起点站
            config.BUS_STATUS = 3
        elif target_platform == len(config.platform_pk)-1:  # 到达终点站
            config.BUS_STATUS = 4
        elif target_platform > 0:
            if target_platform > config.NOW_PLATFORM:
                config.BUS_STATUS = 1
            elif target_platform < config.NOW_PLATFORM:
                config.BUS_STATUS = 2
            else:
                return   # 保持状态不变
        else:
            return       # 保持状态不变

    elif mode == 2:               # 根据离站信号来判断下一状态
        if target_platform == 0:  # 离开起点站
            config.BUS_STATUS = 1
        elif target_platform == len(config.platform_pk)-1:  # 离开终点站
            config.BUS_STATUS = 2
        elif target_platform > 0:
            if target_platform > config.OLD_PLATFORM:
                config.BUS_STATUS = 1
            elif target_platform < config.OLD_PLATFORM:
                config.BUS_STATUS = 2
            else:
                return   # 保持状态不变
        else:
            return  # 保持状态不变
    else:
        return      # 保持状态不变

'''
function：
    根据当前所在站台的下标，获取下一站的列表下标
'''
def get_next_platform(now_platform):
    if now_platform == 0:
        return 1
    elif now_platform == len(config.platform_name)-1:
        return now_platform-1
    elif now_platform>0 and config.BUS_STATUS == 1:
        return now_platform + 1
    elif now_platform>0 and config.BUS_STATUS == 2:
        return now_platform - 1
    else:
        return config.NEXT_PLATFORM


'''
function：
    检查车辆是否到达起点站：车辆始发时，必须从起点站发车，否则不进行gps检测

修改：
    修改站点为起点站

return：
    1表示成功，0表示失败
'''
def start_bus():
    gps_value = read_gps()            # 读取一次gps数据
    if gps_value == 1:                # gps数据有效
        come_value = come_platform()  # 进行一次到站判断
        if come_value == 0:           # 如果到站判断给出的结果是到达起点站
            print('[start_bus] 司机已到达起点站，开始正式发车')
            logging.debug('[start_bus] 司机已到达起点站，开始正式发车')
            config.BUS_STATUS = 3    # 状态改为起点等待
            config.NOW_PLATFORM = 0  # 当前站台改为起始站
            config.OLD_PLATFORM = 1  # 理论上的上一站
            config.NEXT_PLATFORM = 1 # 理论上的下一站
            send_come_message(come_value)   #发送一次到站信息
            return 1
        else:
            print('[start_bus] 司机正在赶往起点站')
            #logging.debug('[start_bus] 司机正在赶往起点站')
            return 0
    else:
        print('[start_bus] GPS数据无效')
        logging.debug('[start_bus] GPS数据无效')
        return 0


'''
function：
    读取一次有效的gps数据，写入config中，
    如果此次读取的数据有效，函数返回1，读取的数据无效，函数返回0
'''
def read_gps():

    if config.TEST == '0':
        '''
        正式版gps获取数据代码
        '''
        while True:
            line = str(str(SER.readline())[2:])       # 头部的  b‘ 两个字符不要
            if line.startswith('$GPRMC'):             # 寻找有地理坐标的那一行数据（使用gprmc）
                line = line.replace('\\r\\n\'', '')   # 去除字符串末尾的无用字符
                try:
                    rmc = pynmea2.parse(line)             # 调库，解析
                except pynmea2.nmea.ParseError:
                    print('因为pynema2解析错误导致的 GPS数据解析错误，开始GPS异常处理')
                    config.GPS_AVAILABLE = 0             # GPS读数无效
                    config.GPS_FAIL_COUNT = config.GPS_FAIL_COUNT + 1
                    config.GPS_TOTAL = config.GPS_TOTAL + 1
                    logging.debug('[read_gps] 因为pynema2解析错误导致的 GPS读数无效   ' + 'GPS总采集次数： ' + str(config.GPS_TOTAL) + '  GPS总失效次数： ' + str(config.GPS_FAIL_COUNT))
                    logging.debug(str(pynmea2.nmea.ParseError))
                    return 0


                if rmc.longitude != 0.0 and rmc.latitude != 0.0:  # 为了防止gps信号中断，如果信号中断，就保持旧的地理坐标
                    config.OLD_LONGITUDE = config.LONGITUDE  # 保存旧的经纬度
                    config.OLD_LATITUDE = config.LATITUDE
                    
                    config.LONGITUDE = rmc.longitude + config.LON_REVISION      # 获取新的经纬度 + GPS数据修正
                    config.LATITUDE = rmc.latitude + config.LA_REVISION
                  
                    config.GPS_AVAILABLE = 1              # GPS数据有效
                    print('[gps_send]：GPS读取到的数据：经度 ', config.LONGITUDE, '  纬度  ', config.LATITUDE)
                    config.GPS_TOTAL = config.GPS_TOTAL + 1

                    if config.gps_count > 10:             # 每隔10次有效数据，向日志记录一下gps位置
                        logging.debug('[read_gps] GPS读取到的数据：经度 ' + str(config.LONGITUDE) + '  纬度  ' + str(config.LATITUDE))
                        config.gps_count = 0
                    else:
                        config.gps_count = config.gps_count + 1
                    return 1
                else:
                    config.GPS_AVAILABLE = 0             # GPS读数无效
                    config.GPS_FAIL_COUNT = config.GPS_FAIL_COUNT + 1
                    config.GPS_TOTAL = config.GPS_TOTAL + 1
                    logging.debug('[read_gps] GPS读数无效   ' + 'GPS总采集次数： ' + str(config.GPS_TOTAL) + '  GPS总失效次数： ' + str(config.GPS_FAIL_COUNT))
                    return 0
    else:
        '''
            测试代码，模拟读取一次gps
        '''
        if config.LATITUDE == 0.0 and config.LONGITUDE == 0.0:
            config.LONGITUDE = 126.195
            config.LATITUDE = 45.195
            config.gps_flag = 1
        elif config.LATITUDE > 45.215 and config.LONGITUDE>126.215 and config.gps_flag == 1:
            config.OLD_LATITUDE = config.LATITUDE
            config.OLD_LONGITUDE = config.LONGITUDE
            config.LATITUDE = 45.215
            config.LONGITUDE = 126.215
            config.gps_flag = 0
        elif config.LATITUDE < 45.2 and config.LONGITUDE<126.2 and config.gps_flag == 0:
            config.OLD_LATITUDE = config.LATITUDE
            config.OLD_LONGITUDE = config.LONGITUDE
            config.LATITUDE = 45.2
            config.LONGITUDE = 126.2
            config.gps_flag = 1
        elif config.gps_flag == 1:  # 正向
            config.OLD_LONGITUDE = config.LONGITUDE  # 保存旧的经纬度
            config.OLD_LATITUDE = config.LATITUDE
            config.LONGITUDE = config.LONGITUDE + 0.0003      # 获取新的经纬度
            config.LATITUDE = config.LATITUDE + 0.0003
        elif config.gps_flag == 0:  # 逆向
            config.OLD_LONGITUDE = config.LONGITUDE  # 保存旧的经纬度
            config.OLD_LATITUDE = config.LATITUDE
            config.LONGITUDE = config.LONGITUDE - 0.0003      # 获取新的经纬度
            config.LATITUDE = config.LATITUDE - 0.0003
        print('[gps_send]：GPS读取到的数据：经度 ', config.LONGITUDE, '  纬度  ', config.LATITUDE)
        config.GPS_AVAILABLE = 1 # GPS数据有效
        return 1


'''
向服务器发送到站的json报文
    {'driver_id':123, 'bus_id':'a123', 'platform_name':'shshj', 'status': 1/2/3/4, 'type':'in/out'}
'''
def send_come_message(ret_plat_num):

    json_context = {'driver_id':config.DRIVER_ID, 'bus_id':config.BUS_ID, 'type':'in'}
    json_context['platform_name'] = config.platform_name[ret_plat_num]
    if config.BUS_STATUS <= 0:
        json_context['status'] = 0
    else:
        json_context['status'] = config.BUS_STATUS

    logging.debug('[send_come_message]发送车辆到站信息，车辆到达站点： ' + str(config.platform_name[ret_plat_num]))
    try:
        response = requests.post(url= config.URL+config.platform_arrive_URL, json=json_context)  # 向服务器发送到达站点的信息
        response.raise_for_status()                                       # 如果响应状态码不是 200，就主动抛出异常
    except requests.RequestException as e:
        print('网络异常，GPS站点发送失败')
        logging.debug('网络异常，GPS站点发送失败')
        logging.error(str(e))
        return -1
    else:
        #response = requests.post(url= config.URL+config.platform_arrive_URL, json=json_context)  # 向服务器发送到达站点的信息
        ret_type = json.loads(response.text).get('type')
        logging.debug('[到站] GPS读取到的数据：经度 ' + str(config.LONGITUDE) + '  纬度  ' + str(config.LATITUDE))
        return 1
    #print('[gps_send]：', ret_type)


'''
向服务器发送离站的json报文
    {'driver_id':123, 'bus_id':'a123', 'platform_name':'shshj', 'status': 1/2/3/4, 'type':'in/out'}
'''
def send_leave_message(leave_plat_num):
    json_context = {'driver_id':config.DRIVER_ID, 'bus_id':config.BUS_ID, 'status': config.BUS_STATUS, 'type':'out'}
    json_context['platform_name'] = config.platform_name[leave_plat_num]

    logging.debug('[send_leave_message]发送车辆离站信息，车辆离开站点： ' + str(config.platform_name[leave_plat_num]))
    try:
        response = requests.post(url=config.URL+config.platform_arrive_URL, json=json_context)  # 向服务器发送到达站点的信息
        response.raise_for_status()                                       # 如果响应状态码不是 200，就主动抛出异常
    except requests.RequestException as e:
        print('网络异常，GPS站点发送失败')
        logging.debug('网络异常，GPS站点发送失败')
        logging.error(str(e))
        return -1
    else:
        #response = requests.post(url=config.URL+config.platform_arrive_URL, json=json_context)  # 向服务器发送到达站点的信息
        ret_type = json.loads(response.text).get('type')
        logging.debug('[离站] GPS读取到的数据：经度 ' + str(config.LONGITUDE) + '  纬度  ' + str(config.LATITUDE))
        return 1




'''
function：
    根据距离，返回即将到站的列表下标(不改变任何值)
return:
    车辆即将到达的站点的列表下标序号（如果没有到达任何站，或者任然还没出站，则返回-1）
'''
def come_platform():
    plat_num = len(config.platform_name)        # 站点个数
    THRESHOLD_2 = config.THRESHOLD ** 2

    for i in range(plat_num):
        distance_2 = (config.LONGITUDE - config.platform_longitude[i])**2 + (config.LATITUDE - config.platform_latitude[i])**2
        if distance_2 < THRESHOLD_2:
            if i == config.NOW_PLATFORM:    # 和当前站点（最近一次到达的站）相同，则忽略
                continue
            else:                           # 和当前站点不同，则说明到达一个新站点
                return i

        '''
        if (math.fabs(config.LONGITUDE - config.platform_longitude[i]) < config.THRESHOLD) and (math.fabs(config.LATITUDE - config.platform_latitude[i]) < config.THRESHOLD):
            if i == config.NOW_PLATFORM:    # 和当前站点（最近一次到达的站）相同，则忽略
                continue
            else:                           # 和当前站点不同，则说明到达一个新站点
                return i
                '''
    return -1                               # 遍历一圈，没到达任何新站点，则返回-1                  


'''
function：
    根据距离，返回即将出站的列表下标（不改变任何值）
return:
    车辆即将离开的站点的列表序号（如果没有离开任何站，或者任然还没出站，则返回-1）
    HAVE_SEND_LEAVE_MESSAGE 表示已经发送过离站信息的标志
'''
def leave_platform():
    THRESHOLD_2 = config.THRESHOLD ** 2
    if config.NOW_PLATFORM >= 0 and config.HAVE_SEND_LEAVE_MESSAGE == 0:   
        i = config.NOW_PLATFORM
        distance_2 = (config.LONGITUDE - config.platform_longitude[i])**2 + (config.LATITUDE - config.platform_latitude[i])**2
        if distance_2 > THRESHOLD_2:
            return i  # 距离大于域值，说明离站
        else:
            return -1  # 还未离站
        '''
        if (math.fabs(config.LONGITUDE - config.platform_longitude[i]) > config.THRESHOLD) and (math.fabs(config.LATITUDE - config.platform_latitude[i]) > config.THRESHOLD):
            return i  # 距离大于域值，说明离站
        else:
            return -1  # 还未离站
            '''
    else:
        return -1  # 还未到达任何站，所以也不能离站



'''
function: 
  1.查询站点的gps信息（司机上班打卡后，查询一次，写入config文件中platform_name列表保存）

上传的报文：
    {'driver_id': 'xxxxxxxx', 'bus_id': 'xxxxxx'}

服务器返回的报文(一个包含多个字典的列表)：
    [  {'pk':'***', 'platform': 'name****', 'longitude': 118.222, 'latitude': 45.365},  {...} ... ]

注意：
    站点的列表顺序必须有序，从起点到终点
'''
def platform_query():
    
    json_platform = {'driver_id':config.DRIVER_ID, 'bus_id':config.BUS_ID }   # 待发送的json报文

    try:
        response = requests.post(url=config.URL+config.platform_URL, json=json_platform)     # 上传至服务器验证（阻塞）,同时接受返回回来的数据
        response.raise_for_status()                                       # 如果响应状态码不是 200，就主动抛出异常
    except requests.RequestException as e:
        print('网络异常，站点查询失败')
        logging.debug('网络异常，站点查询失败')
        logging.error(str(e))
        return -1

    #response = requests.post(url=config.URL+config.platform_URL, json=json_platform)     # 上传至服务器验证（阻塞）,同时接受返回回来的数据
    platform_list = json.loads(response.text)                                 # 解析出来的是一个list，list中每个元素是一个字典
    print('[platform_query]:收到的站台信息  ', platform_list)
    logging.debug('[platform_query]:收到的站台信息  ' + str(platform_list) )

    for platform_dict in platform_list:                                       # 遍历列表中的每个字典，取出来，存储到config中的列表中去
        if ('platform' in platform_dict) and ('longitude' in platform_dict) and ('latitude' in platform_dict):  # 检查数据合法性
            config.platform_pk.append(platform_dict['pk'])
            config.platform_name.append(platform_dict['platform'])
            config.platform_longitude.append(platform_dict['longitude'])
            config.platform_latitude.append(platform_dict['latitude'])
    print('[platform_query] 所有站点  ', config.platform_name)
    return len(config.platform_name)

    

if __name__ == "__main__":
    config.BUS_ID = '黑1234'
    gps_send()


    
    

